﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using DotNet.DevOps.Tasks.Util;
using Microsoft.Build.Framework;

namespace DotNet.DevOps.Tasks.Log4Net
{
    /// <summary>
    /// Abstract class used to implement log4net logger tasks.
    /// </summary>
    public abstract class BaseLevelUpdater
    {
        /// <summary>
        /// File(s) to updates.
        /// </summary>
        [Required]
        public string Files { get; set; }

        /// <summary>
        /// The level to be set, consult log4net's documentation for correct value. Ex. INFO, WARN, DEBUG.
        /// </summary>
        [Required]
        public string Level { get; set; }

        protected string NodeName { get; set; }
        protected string NodeAttribute { get; set; }
        protected string SubNodeName { get; set; }

        protected ILog Log { get; set; }

        protected BaseLevelUpdater(string nodeName, string nodeAttr, string subNodeName)
        {
            NodeName = nodeName;
            NodeAttribute = nodeAttr;
            SubNodeName = subNodeName;
        }

        public bool Execute()
        {
            try
            {
                var filenames = Files.GetValidFilenames();

                filenames.ToList().ForEach(InsertOrUpdate);
            }
            catch (XmlException xmlEx)
            {
                Log.LogError(new Exception(string.Format("There's a problem with your input file. {0}", xmlEx.Message)));
                return false;
            }
            catch (Exception ex)
            {
                Log.LogError(ex);
                return false;
            }

            return true;
        }

        private void InsertOrUpdate(string filename)
        {
            var xDoc = XDocument.Load(filename);

            Log.LogInfo(string.Format("Modifying: {0}", filename));

            var matchingNodes = GetMatchingNodes(xDoc);

            ValidateNodesFound(matchingNodes);

            UpdateNodes(matchingNodes);

            xDoc.Save(filename);
        }

        protected abstract IEnumerable<XElement> GetMatchingNodes(XDocument xDoc);

        private void ValidateNodesFound(IEnumerable<XElement> nodesFound)
        {
            CheckIfNewSettingCanBeAdded(nodesFound.Count());
            CheckIfTheresToManyIdenticalSettings(nodesFound.Count());
            CheckIfNodeContainLevelElement(nodesFound);
        }

        private void CheckIfNewSettingCanBeAdded(int matchingNodesFound)
        {
            if (matchingNodesFound == 0)
            {
                throw new Exception(string.Format("No match searching for {0}{1} with subelement <{2} ... />", 
                                                    NodeName, 
                                                    (NodeAttribute != null ? string.Format("=\"{0}\"", NodeAttribute) : ""),
                                                    SubNodeName));
            }
        }

        private void CheckIfTheresToManyIdenticalSettings(int matchingNodesFound)
        {
            if (matchingNodesFound > 1)
            {
                throw new Exception(string.Format("Too many matches searching for {0}{1} with subelement <{2} ... />! {3} found, only expected 1.",
                                                    NodeName,
                                                    (NodeAttribute != null ? string.Format("=\"{0}\"", NodeAttribute) : ""),
                                                    SubNodeName,
                                                    matchingNodesFound));
            }
        }

        private void CheckIfNodeContainLevelElement(IEnumerable<XElement> nodesFound)
        {
            var subNodes = nodesFound.First().Elements().Where(sub => sub.Name.ToString().ToLower() == "level").Count();

            if (subNodes != 1)
            {
                throw new Exception(string.Format("{0}{1} does not contain correct number of subelement <{2} ... />! {3} found, only expected 1.",
                                                    NodeName,
                                                    (NodeAttribute != null ? string.Format("=\"{0}\"", NodeAttribute) : ""),
                                                    SubNodeName,
                                                    subNodes));
            }
        }

        private void UpdateNodes(IEnumerable<XElement> matchingNodes)
        {
            matchingNodes.ToList().ForEach(SetValue);
        }

        private void SetValue(XElement node)
        {
            var subNode = node.Elements().Where(sub => sub.Name.ToString().ToLower() == SubNodeName);
            var oldLevel = subNode.Attributes().Where(atr => atr.Name.ToString().ToLower() == "value").First().Value;

            subNode.Attributes().Where(atr => atr.Name.ToString().ToLower() == "value").First().SetValue(Level);

            var loggerName = (node.Attributes("name").FirstOrDefault() == null ? NodeName : string.Format("({0})", node.Attributes("name").First().Value));

            Log.LogInfo(string.Format("Updated {0} logger {1}: {2} -> {3}", loggerName, SubNodeName, oldLevel, Level));
        }


        public override string ToString()
        {
            return string.Format("{0} Level={1}, Files={2}", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(NodeName), Level, Files);
        }
    }
}
